package edu.ntnu.superrss.utils.feedgrabber;

import com.sun.syndication.feed.synd.SyndEntry;
import com.sun.syndication.feed.synd.SyndFeed;
import com.sun.syndication.fetcher.FeedFetcher;
import com.sun.syndication.fetcher.FetcherEvent;
import com.sun.syndication.fetcher.FetcherException;
import com.sun.syndication.fetcher.FetcherListener;
import com.sun.syndication.fetcher.impl.FeedFetcherCache;
import com.sun.syndication.fetcher.impl.HashMapFeedInfoCache;
import com.sun.syndication.fetcher.impl.HttpURLFeedFetcher;
import com.sun.syndication.io.FeedException;
import edu.ntnu.superrss.models.SyndEntryImpl;
import edu.ntnu.superrss.models.SyndFeedExt;
import edu.ntnu.superrss.models.SyndFeedImpl;
import edu.ntnu.superrss.utils.HibernateUtil;
import edu.ntnu.superrss.utils.SyndEntrySort;
import java.io.IOException;
import java.net.URL;
import java.util.Collections;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.hibernate.Session;
import org.hibernate.Transaction;

/**
 * 
 * @author Sigurd Fosseng <sigurd at fosseng.net>, Hallvard
 */
public class UrlWorker implements Runnable {

    private URL url;
    private SyndFeedImpl feed;

    public UrlWorker(URL url) {
        this.url = url;
    }

    /**
     * This utilizes RomeFetcher to grab a feed, converts it to our
     * hibernate specific implementation, and adds information infomation
     * specified by our SyndFeedExt.
     * if listener is not null, send FetcherEvents to that listener.
     * @param url
     * @param listener
     * @return SyndFeedImpl with the necesarry data.
     */
    public static SyndFeed grabFeed(URL url, FetcherListener listener) throws IllegalArgumentException, IOException, FeedException, FetcherException {
        SyndFeed feed = null;
        if (url == null) {
            throw new IllegalArgumentException("Url can not be null");
        }

        FeedFetcherCache feedInfoCache = HashMapFeedInfoCache.getInstance(); //caching feeds to enable conditional get
        FeedFetcher feedFetcher = new HttpURLFeedFetcher(feedInfoCache);

        if (listener != null) {
            feedFetcher.addFetcherEventListener(listener);
        }

        feed = feedFetcher.retrieveFeed(url);

        //convert to our SyndFeedImpl for Hibernate
        //This should perhaps be done in fetcher.properties instead of here.
        //If we can do it that way we should be able to avoid copyFrom entierly -S
        SyndFeedExt tmpfeed = new SyndFeedImpl();
        tmpfeed.copyFrom(feed);
        tmpfeed.setUrl(url.toExternalForm());
        tmpfeed.setLastUpdate(new Date());
        return tmpfeed;
    }

    /**
     * Calls grabFeed(Url url, FetcherListener listener) with null as listener.
     * @param url
     * @return
     * @throws IllegalArgumentException
     * @throws IOException
     * @throws FeedException
     * @throws FetcherException
     */
    public static SyndFeed grabFeed(URL url) throws IllegalArgumentException, IOException, FeedException, FetcherException {
        return grabFeed(url, null);
    }

    public void run() {
        try {
            grabFeed(url, new FetcherEventListenerImpl());
            //Vi bør kanskje foreta oss noe når vi får exeptions.
        } catch (IllegalArgumentException ex) {
            Logger.getLogger(UrlWorker.class.getName()).log(Level.SEVERE, null, ex);
        } catch (IOException ex) {
            Logger.getLogger(UrlWorker.class.getName()).log(Level.SEVERE, null, ex);
        } catch (FeedException ex) {
            Logger.getLogger(UrlWorker.class.getName()).log(Level.SEVERE, null, ex);
        } catch (FetcherException ex) {
            Logger.getLogger(UrlWorker.class.getName()).log(Level.SEVERE, null, ex);
        }

    }

    static class FetcherEventListenerImpl implements FetcherListener {

        public void fetcherEvent(FetcherEvent event) {
            String eventType = event.getEventType();

            //Ny versjon av feeden lastet ned.
            if (FetcherEvent.EVENT_TYPE_FEED_RETRIEVED.equals(eventType)) {
                Logger.getLogger(UrlWorker.class.getName()).log(Level.INFO, "UrlWorker:feed downloaded " + event.getUrlString());
                SyndFeed tmpFeed = event.getFeed();
                SyndFeedImpl newFeed = new SyndFeedImpl();
                newFeed.copyFrom(tmpFeed);


                //Session-pr-request. Jeg kontrollerer åpning og lukking av sessions for å være sikker på flertrådstøtte.
                Session session = HibernateUtil.getSessionFactory().openSession();
                Transaction tx = null;
                try {
                    tx = session.beginTransaction();
                    SyndFeedImpl oldFeed = (SyndFeedImpl) session.createQuery("From SyndFeedImpl where url = ?").setParameter(0, event.getUrlString()).uniqueResult();
                    if (oldFeed != null) {
                        //oppdater LastUpdate på den gamle feeden fra databasen
                        oldFeed.setLastUpdate(new Date());

                        //Vi skal lagre den gamle Feeden i databasen, men vi må oppdatere den med den nye informasjonen.
                        List newEntries = newFeed.getEntries();
                        List oldEntries = oldFeed.getEntries();
                        session.evict(oldEntries);
                        for (Iterator it = newEntries.iterator(); it.hasNext();) {
                            SyndEntryImpl newEntry = (SyndEntryImpl) it.next();
                            //Hente tilsvarende entryen som er i databasen. URI er god identifikator
                            Collections.sort(oldEntries, SyndEntrySort.URI_ORDER);
                            int index = Collections.binarySearch(oldEntries, newEntry, SyndEntrySort.URI_ORDER);
                            if (index > 0) {
                                SyndEntryImpl oldEntry = (SyndEntryImpl) oldEntries.get(index);
                                //må huske Comments, thumbsup og EntryID
                                newEntry.setComments(oldEntry.getComments());
                                newEntry.setThumbsUp(oldEntry.getThumbsUp());
                                newEntry.setEntryID(oldEntry.getEntryID());

                                //glem den gamle SyndEntryImpl slik at vi kan lagre den nye
                                session.evict(oldEntry);
                            }
                        }
                        oldFeed.setEntries(newEntries);
                        newFeed = oldFeed;
                    }


                    session.saveOrUpdate(newFeed);
                    session.flush();
                    tx.commit();
                } catch (RuntimeException e) {
                    tx.rollback();
                    throw e; // TODO: LOG4J
                } finally {
                    session.close();
                }
                //Feed er ikke oppdatert siden sist.
            } else if (FetcherEvent.EVENT_TYPE_FEED_UNCHANGED.equals(eventType)) {
                Session session = HibernateUtil.getSessionFactory().openSession();
                Transaction tx = null;
                try {
                    tx = session.beginTransaction();
                    SyndFeedImpl oldFeed = (SyndFeedImpl) session.createQuery("From SyndFeedImpl where url = ?").setParameter(0, event.getUrlString()).uniqueResult();
                    oldFeed.setLastUpdate(new Date());
                    session.save(oldFeed);
                    session.flush();
                    tx.commit();
                } catch (RuntimeException e) {
                    tx.rollback();
                    throw e; // TODO: LOG4J
                } finally {
                    session.close();
                }
                Logger.getLogger(UrlWorker.class.getName()).log(Level.INFO, "UrlWorker: feed was unchanged " + event.getUrlString());
            }
        }
    }
}
